﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace App.Validate
{
    public static class Validation
    {
        private static Dictionary<NamedProperty, IEnumerable<Validator>> validators = new Dictionary<NamedProperty, IEnumerable<Validator>>();


        private static IEnumerable<Validator> GetValidators(string ruleName, PropertyInfo property)
        {
            var key = new NamedProperty(ruleName, property);
            if (validators.ContainsKey(key))
            {
                return validators[key];
            }
            lock (typeof(Validation))
            {
                if (validators.ContainsKey(key))
                {
                    return validators[key];
                }
                validators[key] = DoGetValidators(ruleName, property);
                return validators[key];
            }
        }

        private static IEnumerable<Validator> DoGetValidators(string ruleName, PropertyInfo property)
        {
            var validatorAttributes = new List<ValidatorAttribute>();
            var validatorElementAttributes = new List<ValidatorElementAttribute>();

            foreach (var attribute in property.GetCustomAttributes(typeof(ValidatorAttribute), true))
            {
                ValidatorAttribute validatorAttribute = (ValidatorAttribute)attribute;
                if (validatorAttribute.RuleName == ruleName)
                {
                    validatorAttribute.FormatMessage(property);
                    validatorAttributes.Add(validatorAttribute);
                }
            }

            foreach (var attribute in property.GetCustomAttributes(typeof(ValidatorElementAttribute), true))
            {
                ValidatorElementAttribute validatorAttribute = (ValidatorElementAttribute)attribute;
                validatorElementAttributes.Add(validatorAttribute);
            }

            foreach (var attribute in validatorAttributes)
            {
                if (attribute is CompositeValidatorAttribute)
                {
                    CompositeValidatorAttribute compositeValidatorAttribute = (CompositeValidatorAttribute)attribute;
                    var validators = new List<Validator>();
                    var validatorElements = from element in validatorElementAttributes
                                            where compositeValidatorAttribute.ValidatorElements.Contains(element.Name)
                                            select element;
                    foreach (var element in validatorElements)
                    {
                        CompositeValidatorElementAttribute compositeValidatorElementAttribute = element as CompositeValidatorElementAttribute;
                        if (null != compositeValidatorElementAttribute)
                        {
                            validators.Add(GetCompositeValidator(compositeValidatorElementAttribute, validatorElementAttributes));
                        }
                        else
                        {
                            validators.Add(element.CreateValidator());
                        }
                    }
                    yield return compositeValidatorAttribute.CreateCompositeValidator(validators);
                }
                else
                {
                    yield return attribute.CreateValidator();
                }
            }
        }

        private static CompositeValidator GetCompositeValidator(CompositeValidatorElementAttribute validatorElementAttribute, IEnumerable<ValidatorElementAttribute> validatorElementAttributes)
        {
            var validators = new List<Validator>();
            var validatorElements = from element in validatorElementAttributes
                                    where validatorElementAttribute.ValidatorElements.Contains(element.Name)
                                    select element;
            foreach (var element in validatorElements)
            {
                if (element is CompositeValidatorElementAttribute)
                {
                    CompositeValidatorElementAttribute compositeValidatorElementAttribute = (CompositeValidatorElementAttribute)element;
                    var nestedValidators = new List<Validator>();
                    var nestedValidatorElements = from nestedElement in validatorElementAttributes
                                                  where compositeValidatorElementAttribute.ValidatorElements.Contains(element.Name)
                                                  select nestedElement;
                    foreach (var nestedElement in nestedValidatorElements)
                    {
                        CompositeValidatorElementAttribute nestedCompositeValidatorElementAttribute = nestedElement as CompositeValidatorElementAttribute;
                        if (null != nestedCompositeValidatorElementAttribute)
                        {
                            nestedValidators.Add(GetCompositeValidator(nestedCompositeValidatorElementAttribute, validatorElementAttributes));
                        }
                        else
                        {
                            nestedValidators.Add(nestedElement.CreateValidator());
                        }
                    }
                    validators.Add(compositeValidatorElementAttribute.CreateCompositeValidator(nestedValidators));
                }
                else
                {
                    validators.Add(element.CreateValidator());
                }
            }

            return validatorElementAttribute.CreateCompositeValidator(validators);
        }

        public static bool Validate(object value, out List<ValidationError> validationErrors)
        {
            return Validate(value, string.Empty, out validationErrors);
        }

        public static bool Validate(object value, string ruleName, out List<ValidationError> validationErrors)
        {
            validationErrors = new List<ValidationError>();
            Guard.ArgumentNotNull(value, "value");
            PropertyInfo[] pif = value.GetType().GetProperties();
            foreach (var property in pif)
            {
                var validators = GetValidators(ruleName, property);
                if (validators.Count() > 0)
                {
                    var propertyValue = property.GetValue(value, null);
                    foreach (var validator in validators)
                    {
                        validator.FormatMessage(propertyValue);
                        var error = validator.Validate(propertyValue);
                        if (null != error)
                        {
                            error.PropertyName = property.Name;
                            validationErrors.Add(error);
                        }
                    }
                }
            }
            return validationErrors.Count() == 0;
        }

        public static bool Validate(object value, string ruleName, out Dictionary<String, List<ValidationError>> validationErrors)
        {
            validationErrors = new Dictionary<String, List<ValidationError>>();
            Guard.ArgumentNotNull(value, "value");
            PropertyInfo[] pif = value.GetType().GetProperties();
            foreach (var property in pif)
            {
                var validators = GetValidators(ruleName, property);
                if (validators.Count() > 0)
                {
                    var propertyValue = property.GetValue(value, null);
                    foreach (var validator in validators)
                    {
                        validator.FormatMessage(propertyValue);
                        var error = validator.Validate(propertyValue);
                        if (null != error)
                        {
                            error.PropertyName = property.Name;
                            if (!validationErrors.ContainsKey(error.PropertyName))
                            {
                                List<ValidationError> list = new List<ValidationError>();
                                validationErrors.Add(error.PropertyName, list);
                            }

                            validationErrors[error.PropertyName].Add(error);
                        }
                    }
                }
            }
            return validationErrors.Count() == 0;
        }

    }

    internal class NamedProperty
    {
        public string RuleName { get; set; }
        public PropertyInfo Property { get; set; }

        public NamedProperty(string name, PropertyInfo property)
        {
            this.RuleName = name ?? string.Empty;
            Guard.ArgumentNotNull(property, "property");
            this.Property = property;
        }

        public override int GetHashCode()
        {
            return this.RuleName.GetHashCode() ^ this.Property.GetHashCode();
        }
    }
}
